home *** CD-ROM | disk | FTP | other *** search
- /*
- File: FBATask.c
-
- Contains:
-
- Written by: Greg Sutton
-
- Copyright: Copyright © 1996-1999 by Apple Computer, Inc., All Rights Reserved.
-
- You may incorporate this Apple sample source code into your program(s) without
- restriction. This Apple sample source code has been provided "AS IS" and the
- responsibility for its operation is yours. You are not permitted to redistribute
- this Apple sample source code as "Apple sample source code" after having made
- changes. If you're going to re-distribute the source, we require that you make
- it clear in the source that the code was descended from Apple sample source
- code, but that you've made changes.
-
- Change History (most recent first):
- 7/21/1999 Karl Groethe Updated for Metrowerks Codewarror Pro 2.1
-
-
- */
-
-
- #include "FBATask.h"
-
- #include "FBA.h"
- #include "FBAAppleEvents.h"
- #include "FBALists.h"
-
- #include <Errors.h>
- #include <Resources.h>
- #include <Processes.h>
- #include <TextUtils.h>
-
-
- typedef struct
- {
- long theState; // We check for folder changes in stages - this is the state
- unsigned long theLastTime; // The last time a volume check was initiated.
- long theResultIndex; // For stpping through the results of the PBCatSearch().
- FSSpec* theInfoDir; // FSSpec for a folder from PBCatSearch() results - this
- // has PBGetCatInfo() called for number of files.
- } SearchStateRec, *SearchStatePtr;
-
-
- // Prototypes
- long DoSearchIdle( void );
- long DoWaitForCatSearch( void );
- long DoCheckCatSearchResults( void );
- long DoSearchNextVolume( void );
-
- static unsigned long GetAverageSleepTime( void );
- static OSErr GetTargetApplicationCreator( OSType* theCreator );
- static Boolean TargetApplicationRunning( ProcessSerialNumber* thePSN );
- static Boolean GetCreatorApplicationPSN( OSType theCreator, ProcessSerialNumber* thePSN );
-
- static void StartCatSearch( WatchVolumePtr theVolPtr );
- static void StartCatInfo( FSSpec* theSpec );
- static Boolean CheckCatInfoResult( ProcessSerialNumber* thePSN );
-
-
- // Constants
- const long MaxFiles = 200; // Maximum number of files received from PBCatSearch()
- const long BufferSize = 16 * 1024; // Buffer size for PBCatSearch()
- const long DiskAccessSleep = 5; // 5 ticks to WaitNextEvent() after calling an
- // asynchronous file access call.
- const long MaxBatchEvents = 20; // Limit the Apple events we send between
- // WaitNextEvent() calls.
-
- enum // Values for theState of our SearchStateRec
- {
- kSearchIdle = 0, kWaitForCatSearch, kCheckCatSearchResults, kSearchNextVolume
- };
-
-
- // Globals
- SearchStateRec gSearchState; // Current state of search.
- CSParam gCSParamRec; // PBCatSearch() parameter block.
- CInfoPBRec gSearchInfo1; // Add these addresses to the parameter block
- CInfoPBRec gSearchInfo2; // for defining search space.
- CInfoPBRec gCInfoPBRec; // PBGetCatInfo() parameter block.
-
- OSType gTargetCreator; // The creator type of the application
- // to send change events to.
-
-
- // Initialise all of the memory we shall use for calls to PBCatSearch().
- // We also set all the PBCatSearch() parameter block fields which
- // will not change.
-
- Boolean InitTask( void )
- {
- OSErr err;
- Boolean result = false;
-
- // Make sure we have a target application.
-
- err = GetTargetApplicationCreator( &gTargetCreator );
- if ( noErr != err ) goto done;
-
- // Set up our global search state
-
- gSearchState.theState = kSearchIdle;
- GetDateTime( &gSearchState.theLastTime );
- gSearchState.theResultIndex = 0;
- gSearchState.theInfoDir = NULL;
-
- // Set up our global PBCatSearch parameter block
-
- gCSParamRec.ioNamePtr = NULL;
-
- gCSParamRec.ioSearchInfo1 = &gSearchInfo1;
- gCSParamRec.ioSearchInfo2 = &gSearchInfo2;
-
- gCSParamRec.ioMatchPtr = (FSSpecPtr)NewPtr( sizeof(FSSpec) * MaxFiles );
- if ( ! gCSParamRec.ioMatchPtr ) goto done;
- gCSParamRec.ioReqMatchCount = MaxFiles;
-
- gCSParamRec.ioOptBuffer = NewPtr( BufferSize );
- if ( ! gCSParamRec.ioOptBuffer ) goto done;
- gCSParamRec.ioOptBufSize = BufferSize;
-
- // Set up PBGetCatInfo() parameter block
-
- gCInfoPBRec.dirInfo.ioResult = 0;
-
- result = true;
-
- done:
- return result;
- }
-
-
- // This routine coordinates the different stages of the search.
- // It returns the number of ticks for sleep in WaitNextEvent().
-
- long DoBackgroundTask( void )
- {
- long result;
-
- switch ( gSearchState.theState )
- {
- case kSearchIdle: // Check it is time, and we can run
- result = DoSearchIdle( ); // off another volume search.
- break;
-
- case kWaitForCatSearch: // Make sure the PBCatSearch() completed
- result = DoWaitForCatSearch( ); // with no errors.
- break;
-
- case kCheckCatSearchResults: // Our PBCatSearch() on the directory
- result = DoCheckCatSearchResults( ); // modification dates has completed
- break; // so act on the results.
-
- case kSearchNextVolume: // Set up the task for the next volume.
- result = DoSearchNextVolume( );
- break;
- }
-
- return result; // Result is in ticks
- }
-
-
- // Check whether it's time to start another search.
- // If it is then start a PBCatSearch.
- // If it isn't then return an updated time for WaitNextEvent().
-
- long DoSearchIdle( void )
- {
- unsigned long now;
- long result;
-
- if ( ! GetHeadVolumePtr( ) )
- return GetAverageSleepTime( ) * 60; // Time to WaitnextEvent() in ticks
-
- GetDateTime( &now ); // Time now in seconds
-
- // Calculate time in seconds then multiply to ticks
- result = (( gSearchState.theLastTime + GetAverageSleepTime( ) ) - now ) * 60;
-
- // Time for another PBCatSearch() check
- if ( result <= 0 )
- {
- StartCatSearch( GetHeadVolumePtr( ) );
-
- result = DiskAccessSleep; // Give over some time to other applications but
- } // we want to check the results of the search soon.
-
- return result;
- }
-
-
- // Check that the PBCatSearch() has completed. Also check the error returned
- // by the PBCatSearch() and act appropriately.
-
- long DoWaitForCatSearch( void )
- {
- long result;
-
- if ( gCSParamRec.ioResult >= 0 ) // The PBCatSearch() still hasn't finished
- return DiskAccessSleep; // try again in a litte while.
-
-
- switch ( gCSParamRec.ioResult )
- {
- case eofErr: // The search completed succesfully so
- gSearchState.theState = kCheckCatSearchResults;
- break; // we can now look at the results.
-
- case catChangedErr: // The catalog search as interrupted so results
- case afpCatalogChanged: // could be miss some changes.
- gSearchState.theState = kSearchIdle; // The idle call will kick off
- break; // another PBCatSearch() on the same
- // volume straight away.
-
- default: // Some kind of problem with the search
- // try going onto the next volume.
- gSearchState.theState = kSearchNextVolume;
- }
-
-
- result = DoBackgroundTask( ); // Act on the result straight away.
-
- return result;
- }
-
-
- // The PBCatSearch() has completed - check the results in the parameter block.
- // This function
-
- long DoCheckCatSearchResults( void )
- {
- ProcessSerialNumber aPSN;
- FSSpec* anFSSpecPtr;
- WatchFolderPtr aWatchFolderPtr;
- long eventsSent = 0;
- long result = 0;
-
- if ( TargetApplicationRunning( &aPSN ) )
- {
- if ( ! CheckCatInfoResult( &aPSN ) )
- return DiskAccessSleep; // Must still be waiting for the PBGetCatInfo()
- // call on a watch folder that's been modified.
-
- // May pick up the loop where we left off
- for ( ; gSearchState.theResultIndex < gCSParamRec.ioActMatchCount; gSearchState.theResultIndex++ )
- {
- anFSSpecPtr = &gCSParamRec.ioMatchPtr[gSearchState.theResultIndex];
- aWatchFolderPtr = FolderInList( anFSSpecPtr );
-
- if ( aWatchFolderPtr )
- {
- StartCatInfo( anFSSpecPtr ); // Start an asychronous PBGetCatInfo() call
- gSearchState.theResultIndex++; // Increment for next DoCheckCatSearchResults() call.
- return DiskAccessSleep; // Return immediately
- }
- else if ( VolumeAndDirIDInList( anFSSpecPtr->vRefNum, anFSSpecPtr->parID ) )
- {
- SendChangeEvent( &aPSN, anFSSpecPtr, kTypeFileModified );
- eventsSent++;
- if ( eventsSent >= MaxBatchEvents )
- {
- gSearchState.theResultIndex++;
- return DiskAccessSleep; // Give the target application a bit of time
- } // to catch up with events sent.
- }
- }
- }
-
- // If we get this far then we have finished looking at all the results.
-
- gSearchState.theState = kSearchNextVolume;
- gSearchState.theResultIndex = 0; // Reset the index for PBCatSearch() results.
-
- result = DoBackgroundTask( ); // Get a time via kSearchNextVolume
- // which goes onto kSearchIdle.
-
- return result;
- }
-
-
- // Update the time searched up to on the current volume.
- // Set the next volume to search as the next in the volume list.
- // Set the task back to kSearchIdle.
-
- long DoSearchNextVolume( void )
- {
- WatchVolumePtr tempPtr = GetHeadVolumePtr( );
- unsigned long result = 0;
-
- if ( tempPtr ) // Update the date checked up to
- tempPtr->theLastModCheck = gSearchInfo2.dirInfo.ioDrMdDat;
-
- FirstVolumeToLast( ); // Cycle around volumes containing watched folders
- gSearchState.theState = kSearchIdle; // Back to start
-
- result = DoBackgroundTask( ); // Get a time via kSearchIdle
-
- return result;
- }
-
-
- // Check that we're not in an asychronous call so that we can quit.
- // If we were in an asychronous call then the memory we've allocated for
- // the results will be disposed of if we quit. This may result in writing
- // over someone elses memory.
-
- Boolean CanQuitTask( void )
- {
- Boolean result;
-
- switch ( gSearchState.theState )
- {
- case kWaitForCatSearch: // Check that search is complete
- result = ( gCSParamRec.ioResult < 0 );
- break;
-
- case kCheckCatSearchResults: // Check we're not waiting for a PBGetCatInfo()
- result = ( gCInfoPBRec.dirInfo.ioResult <= 0 );
- break;
-
- default:
- result = true;
- }
-
- return result;
- }
-
-
- // Each cycle must check every volume with watch folders on it. Therefore
- // we need to divide up the cycle time by the number of volumes.
- // Time returned is in seconds.
-
- static unsigned long GetAverageSleepTime( void )
- {
- unsigned long result = 0;
-
- if ( GetNumberOfVolumes( ) )
- {
- result = GetCycleTime( ) / GetNumberOfVolumes( );
- if ( ! result ) // If this happens we've got alot of volumes to check!
- result = 1; // Don't want to return a zero.
- }
- else
- result = GetCycleTime( ); // Someone may add a folder - so check every cycle time
-
- return result;
- }
-
-
- // The creator type of the target application is stored in a 'Targ' resource.
- // This routine just grabs the first resource of this type if there is one.
-
- static OSErr GetTargetApplicationCreator( OSType* theCreator )
- {
- short count;
- Handle aHandle;
- OSErr result = resNotFound;
-
- count = Count1Resources( kTargetAppResource );
- if ( count )
- {
- aHandle = Get1IndResource( kTargetAppResource, 1 );
- if ( aHandle )
- {
- *theCreator = *(OSType *) *aHandle;
- ReleaseResource( aHandle );
- result = noErr;
- }
- }
-
- return result;
- }
-
-
- // Check that the application is running. If a pointer to a ProcessSerialNumber
- // is given it will be filled in if the routine reutrns true.
-
- static Boolean TargetApplicationRunning( ProcessSerialNumber* thePSN )
- {
- ProcessSerialNumber aPSN; // Use this if no ProcessSerialNumber supplied
- Boolean result;
-
- if ( thePSN )
- result = GetCreatorApplicationPSN( gTargetCreator, thePSN );
- else
- result = GetCreatorApplicationPSN( gTargetCreator, &aPSN );
-
- return result;
- }
-
-
- static Boolean GetCreatorApplicationPSN( OSType theCreator, ProcessSerialNumber* thePSN )
- {
- ProcessInfoRec aProcessInfoRec;
- FSSpec processFSSpec;
- Boolean result = false;
-
- // check the current processes to see if the application is already
- // running, and get its process serial number.
- thePSN->lowLongOfPSN = kNoProcess;
- thePSN->highLongOfPSN = 0;
-
- aProcessInfoRec.processInfoLength = sizeof( FSSpec );
- aProcessInfoRec.processName = NULL;
- aProcessInfoRec.processAppSpec = &processFSSpec;
-
- while ( noErr == GetNextProcess( thePSN ) )
- if ( noErr == GetProcessInformation( thePSN, &aProcessInfoRec ) )
- if ( aProcessInfoRec.processSignature == theCreator )
- {
- result = true;
- break;
- }
-
- return result;
- }
-
-
- // This routine sets off a PBCatSearch() on the given volume to see if any of the
- // directories or files have been modified. Directory changes occur when a file or folder is
- // added or removed.
-
- static void StartCatSearch( WatchVolumePtr theVolPtr )
- {
- GetDateTime( &gSearchState.theLastTime ); // Keep for cycle time updates even
- // if target isn't running
-
- if ( TargetApplicationRunning( NULL ) )
- {
- // Set up the variable parts of the PBCatSearch() parameter block.
- // Note that some has been set up in InitTask().
-
- gCSParamRec.ioCompletion = NULL; // Asychronous call with no completion routine
- gCSParamRec.ioVRefNum = theVolPtr->theVolRef;
-
- gSearchInfo1.dirInfo.ioDrMdDat = theVolPtr->theLastModCheck + 1;
- GetDateTime( &gSearchInfo2.dirInfo.ioDrMdDat ); // Up to now
-
- // Searching on the modification date of directories and files
- gCSParamRec.ioSearchBits = fsSBFlMdDat;
-
- gCSParamRec.ioSearchTime = 0; // Allow as much time as needed
- gCSParamRec.ioCatPosition.initialize = 0;
-
- // Update the global search state before we call the asynchronous routine
-
- gSearchState.theState = kWaitForCatSearch; // Where to go next.
-
- (void)PBCatSearch( &gCSParamRec, true ); // Do the modification search
- }
- else // Don't bother accessing the disk - just act like we have done the search
- gSearchState.theState = kSearchNextVolume;
- }
-
-
- static void StartCatInfo( FSSpec* theSpec )
- {
- gSearchState.theInfoDir = theSpec; // Set so we know what directory information
- // we're waiting for.
- gCInfoPBRec.dirInfo.ioCompletion = NULL;// Asychronous call with no completion routine
- gCInfoPBRec.dirInfo.ioNamePtr = theSpec->name;
- gCInfoPBRec.dirInfo.ioVRefNum = theSpec->vRefNum;
- gCInfoPBRec.dirInfo.ioDrDirID = theSpec->parID;
- gCInfoPBRec.dirInfo.ioFDirIndex = 0; // Use ioNamePtr and ioDirID
- gCInfoPBRec.dirInfo.ioACUser = 0; // If this does not compile try using
- // thePB->dirInfo.filler2 or thePB->dirInfo.ioACUser
- // Clear it before calling PBGetCatInfo()
- (void)PBGetCatInfo( &gCInfoPBRec, true );
- }
-
-
- // Check that a PBGetCatInfo() was beng waited for. if it was and it's completed
- // then send off change event to target application.
- // This routine will return false if the result of the PBGetCatInfo() is still being waited for.
-
- static Boolean CheckCatInfoResult( ProcessSerialNumber* thePSN )
- {
- WatchFolderPtr aWatchFolderPtr;
-
- if ( ! gSearchState.theInfoDir ) // No PBGetCatInfo() being waited for.
- return true;
-
- if ( gCInfoPBRec.dirInfo.ioResult > 0 ) // Still waiting for PBGetCatInfo()
- return false;
-
- aWatchFolderPtr = FolderInList( gSearchState.theInfoDir );
-
- if ( aWatchFolderPtr && noErr == gCInfoPBRec.dirInfo.ioResult )
- {
- if ( aWatchFolderPtr->theFileCount < gCInfoPBRec.dirInfo.ioDrNmFls )
- SendChangeEvent( thePSN, gSearchState.theInfoDir, kTypeFileAdded );
- else if ( aWatchFolderPtr->theFileCount > gCInfoPBRec.dirInfo.ioDrNmFls )
- SendChangeEvent( thePSN, gSearchState.theInfoDir, kTypeFileRemoved );
- else // Changed but we don't know whether modified or not
- SendChangeEvent( thePSN, gSearchState.theInfoDir, kTypeFileModified );
-
- aWatchFolderPtr->theFileCount = gCInfoPBRec.dirInfo.ioDrNmFls;
- }
-
- gSearchState.theInfoDir = NULL; // This is set to tell us we are waiting for the PBGetCatInfo()
-
- return true;
- }
-